/*-------------------------------------------------------------------------
 *
 * execUtils.c
 *	  各种执行器实用例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execUtils.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 *		CreateExecutorState		创建/删除执行器工作状态
 *		FreeExecutorState
 *		CreateExprContext
 *		CreateStandaloneExprContext
 *		FreeExprContext
 *		ReScanExprContext
 *
 *		ExecAssignExprContext		计划节点初始化例程的通用代码。
 *		等等
 *
 *		ExecOpenScanRelation	扫描节点初始化例程的通用代码。
 *
 *		ExecInitRangeTable		设置执行器的范围表相关数据。
 *
 *		ExecGetRangeTableRelation	获取某个范围表条目的 Relation。
 *
 *		executor_errposition	报告错误的语法位置。
 *
 *		RegisterExprContextCallback	注册函数关机回调
 *		UnregisterExprContextCallback	注销函数关机回调
 *
 *		GetAttributeByName		在元组中运行时提取列。
 *		GetAttributeByNum
 *
 *	 注意
 *	 该文件传统上是存放杂项的地方。
 *	 执行器支持的东西实际上没有其他地方放。
 */

#include "postgres.h"

#include "access/parallel.h"
#include "access/relscan.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "executor/executor.h"
#include "executor/execPartition.h"
#include "executor/nodeModifyTable.h"
#include "jit/jit.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
#include "partitioning/partdesc.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/typcache.h"


static bool fc_tlist_matches_tupdesc(PlanState *fc_ps, List *fc_tlist, int fc_varno, TupleDesc fc_tupdesc);
static void fc_ShutdownExprContext(ExprContext *fc_econtext, bool fc_isCommit);


/* ----------------------------------------------------------------
 *				 执行器状态和内存管理函数
 * ----------------------------------------------------------------
 */

/* ----------------
 *		CreateExecutorState
 *
 *		创建并初始化一个 EState 节点，它是
 *		整个执行器调用的工作存储的根。
 *
 * 主要地，这创建了每个查询的内存上下文，用于
 * 存放所有在查询结束之前存在的工作数据。
 * 注意，每个查询上下文将成为调用者的
 * CurrentMemoryContext 的子级。
 * ----------------
 */
EState *
CreateExecutorState(void)
{
	EState	   *fc_estate;
	MemoryContext fc_qcontext;
	MemoryContext fc_oldcontext;

	/*
	 * 为此次执行器运行创建每个查询上下文。
	 */
	fc_qcontext = AllocSetContextCreate(CurrentMemoryContext,
									 "ExecutorState",
									 ALLOCSET_DEFAULT_SIZES);

	/*
	 * 在每个查询上下文内创建 EState 节点。这样，我们不
	 * 需要在关机时为其执行单独的 pfree() 操作。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_qcontext);

	fc_estate = makeNode(EState);

	/*
	 * 初始化执行器状态结构的所有字段
	 */
	fc_estate->es_direction = ForwardScanDirection;
	fc_estate->es_snapshot = InvalidSnapshot;	/* 调用者必须初始化此项 */
	fc_estate->es_crosscheck_snapshot = InvalidSnapshot;	/* 不进行交叉检验 */
	fc_estate->es_range_table = NIL;
	fc_estate->es_range_table_size = 0;
	fc_estate->es_relations = NULL;
	fc_estate->es_rowmarks = NULL;
	fc_estate->es_plannedstmt = NULL;

	fc_estate->es_junkFilter = NULL;

	fc_estate->es_output_cid = (CommandId) 0;

	fc_estate->es_result_relations = NULL;
	fc_estate->es_opened_result_relations = NIL;
	fc_estate->es_tuple_routing_result_relations = NIL;
	fc_estate->es_trig_target_relations = NIL;

	fc_estate->es_insert_pending_result_relations = NIL;
	fc_estate->es_insert_pending_modifytables = NIL;

	fc_estate->es_resultrelinfo_extra = NIL;

	fc_estate->es_param_list_info = NULL;
	fc_estate->es_param_exec_vals = NULL;

	fc_estate->es_queryEnv = NULL;

	fc_estate->es_query_cxt = fc_qcontext;

	fc_estate->es_tupleTable = NIL;

	fc_estate->es_processed = 0;

	fc_estate->es_top_eflags = 0;
	fc_estate->es_instrument = 0;
	fc_estate->es_finished = false;

	fc_estate->es_exprcontexts = NIL;

	fc_estate->es_subplanstates = NIL;

	fc_estate->es_auxmodifytables = NIL;

	fc_estate->es_per_tuple_exprcontext = NULL;

	fc_estate->es_sourceText = NULL;

	fc_estate->es_use_parallel_mode = false;

	fc_estate->es_jit_flags = 0;
	fc_estate->es_jit = NULL;

	/*
	 * 返回执行器状态结构
	 */
	MemoryContextSwitchTo(fc_oldcontext);

	return fc_estate;
}


/* ----------------
 *		FreeExecutorState
 *
 *		释放一个EState以及所有剩余的工作存储。
 *
 * 注意：这不负责释放非内存资源，如
 * 打开的关系或缓冲区锁定。但它将关闭EState内仍然活跃的
 * ExprContexts并释放相关的JITed表达式。
 * 这对于EState仅用于表达式评估而未运行完整Plan的情况
 * 是足够的清理。
 *
 * 这可以在任何内存上下文中调用...只要它不是
 * 要释放的上下文之一。
 * ----------------
 */
void FreeExecutorState(EState *fc_estate)
{
	/*
	 * 关闭并释放任何剩余的ExprContexts。我们这样做是为了确保
	 * 任何剩余的关闭回调被调用（因为它们可能需要释放
	 * 在每个查询内存上下文中不是简单内存的资源）。
	 */
	while (fc_estate->es_exprcontexts)
	{
		/*
		 * XXX：似乎应该有比重复的list_delete()更快的实现方式，
		 * 不是吗？
		 */
		FreeExprContext((ExprContext *) linitial(fc_estate->es_exprcontexts),
						true);
		/* FreeExprContext为我们移除了列表链接 */
	}

	/* 释放JIT上下文，如果已分配 */
	if (fc_estate->es_jit)
	{
		jit_release_context(fc_estate->es_jit);
		fc_estate->es_jit = NULL;
	}

	/* 释放分区目录，如果已分配 */
	if (fc_estate->es_partition_directory)
	{
		DestroyPartitionDirectory(fc_estate->es_partition_directory);
		fc_estate->es_partition_directory = NULL;
	}

	/*
	 * 释放每个查询内存上下文，从而释放所有工作
	 * 内存，包括EState节点本身。
	 */
	MemoryContextDelete(fc_estate->es_query_cxt);
}

/*
 * CreateExprContext()和CreateWorkExprContext()的内部实现
 * 允许控制AllocSet参数。
 */
static ExprContext *
fc_CreateExprContextInternal(EState *fc_estate, Size fc_minContextSize,
						  Size fc_initBlockSize, Size fc_maxBlockSize)
{
	ExprContext *fc_econtext;
	MemoryContext fc_oldcontext;

	/* 在每个查询内存上下文中创建ExprContext节点 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	fc_econtext = makeNode(ExprContext);

	/* 初始化ExprContext的字段 */
	fc_econtext->ecxt_scantuple = NULL;
	fc_econtext->ecxt_innertuple = NULL;
	fc_econtext->ecxt_outertuple = NULL;

	fc_econtext->ecxt_per_query_memory = fc_estate->es_query_cxt;

	/*
	 * 在这个上下文中为表达式评估创建工作内存。
	 */
	fc_econtext->ecxt_per_tuple_memory =
		AllocSetContextCreate(fc_estate->es_query_cxt,
							  "ExprContext",
							  fc_minContextSize,
							  fc_initBlockSize,
							  fc_maxBlockSize);

	fc_econtext->ecxt_param_exec_vals = fc_estate->es_param_exec_vals;
	fc_econtext->ecxt_param_list_info = fc_estate->es_param_list_info;

	fc_econtext->ecxt_aggvalues = NULL;
	fc_econtext->ecxt_aggnulls = NULL;

	fc_econtext->caseValue_datum = (Datum) 0;
	fc_econtext->caseValue_isNull = true;

	fc_econtext->domainValue_datum = (Datum) 0;
	fc_econtext->domainValue_isNull = true;

	fc_econtext->ecxt_estate = fc_estate;

	fc_econtext->ecxt_callbacks = NULL;

	/*
	 * 将ExprContext链接到EState，以确保在释放EState时其被关闭。
	 * 因为我们使用lcons（），所以关闭将按照创建的反向顺序发生，
	 * 这可能不是必要的，但没有坏处。
	 */
	fc_estate->es_exprcontexts = lcons(fc_econtext, fc_estate->es_exprcontexts);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_econtext;
}

/* ----------------
 *		CreateExprContext
 *
 *		在EState中为表达式评估创建一个上下文。
 *
 * 执行器运行可能需要多个ExprContexts（我们通常为每个计划节点
 * 创建一个，为每个输出元组处理（如约束检查）创建一个单独的）。
 * 每个ExprContext都有其自己的“每元组”内存上下文。
 *
 * 请注意，我们不对调用者的内存上下文做任何假设。
 * ----------------
 */
ExprContext * CreateExprContext(EState *fc_estate)
{
	return fc_CreateExprContextInternal(fc_estate, ALLOCSET_DEFAULT_SIZES);
}


/* ----------------
 *		CreateWorkExprContext
 *
 * 类似于CreateExprContext，但指定AllocSet的大小与work_mem成合理比例。
 * 如果最大块分配大小过大，单次分配很容易跳过work_mem。
 * ----------------
 */
ExprContext * CreateWorkExprContext(EState *fc_estate)
{
	Size		fc_minContextSize = ALLOCSET_DEFAULT_MINSIZE;
	Size		fc_initBlockSize = ALLOCSET_DEFAULT_INITSIZE;
	Size		fc_maxBlockSize = ALLOCSET_DEFAULT_MAXSIZE;

	/* 选择maxBlockSize不得大于work_mem的1/16 */
	while (16 * fc_maxBlockSize > work_mem * 1024L)
		fc_maxBlockSize >>= 1;

	if (fc_maxBlockSize < ALLOCSET_DEFAULT_INITSIZE)
		fc_maxBlockSize = ALLOCSET_DEFAULT_INITSIZE;

	return fc_CreateExprContextInternal(fc_estate, fc_minContextSize,
									 fc_initBlockSize, fc_maxBlockSize);
}

/* ----------------
 *		CreateStandaloneExprContext
 *
 *		为独立表达式评估创建一个上下文。
 *
 * 以这种方式创建的ExprContext可用于评估不包含Params、子计划或Var引用的表达式
 * （将元组引用放入scantuple字段可能有效，但似乎不明智）。
 *
 * ExprContext结构在调用者的当前内存上下文中分配，后者也成为其“每查询”上下文。
 *
 * 调用者负责在完成后释放ExprContext，
 * 或至少确保调用了所有关机回调（ReScanExprContext()是合适的）。
 * 否则，可能会泄漏非内存资源。
 * ----------------
 */
ExprContext * CreateStandaloneExprContext(void)
{
	ExprContext *fc_econtext;

	/* 在调用者的内存上下文中创建ExprContext节点 */
	fc_econtext = makeNode(ExprContext);

	/* 初始化ExprContext的字段 */
	fc_econtext->ecxt_scantuple = NULL;
	fc_econtext->ecxt_innertuple = NULL;
	fc_econtext->ecxt_outertuple = NULL;

	fc_econtext->ecxt_per_query_memory = CurrentMemoryContext;

	/*
	 * 在这个上下文中为表达式评估创建工作内存。
	 */
	fc_econtext->ecxt_per_tuple_memory =
		AllocSetContextCreate(CurrentMemoryContext,
							  "ExprContext",
							  ALLOCSET_DEFAULT_SIZES);

	fc_econtext->ecxt_param_exec_vals = NULL;
	fc_econtext->ecxt_param_list_info = NULL;

	fc_econtext->ecxt_aggvalues = NULL;
	fc_econtext->ecxt_aggnulls = NULL;

	fc_econtext->caseValue_datum = (Datum) 0;
	fc_econtext->caseValue_isNull = true;

	fc_econtext->domainValue_datum = (Datum) 0;
	fc_econtext->domainValue_isNull = true;

	fc_econtext->ecxt_estate = NULL;

	fc_econtext->ecxt_callbacks = NULL;

	return fc_econtext;
}

/* ----------------
 *		FreeExprContext
 *
 *		释放表达式上下文，包括调用任何剩余的
 *		关机回调。
 *
 * 由于我们释放用于表达式评估的临时上下文，
 * 任何之前计算的按引用传递的表达式结果将消失！
 *
 * 如果isCommit为false，我们是在错误清理中被调用，
 * 不应调用回调，而仅释放内存。
 * （调用回调并将isCommit标志传递给它们可能更好，但这将需要比当前看起来合理的更具侵入性的代码更改。）
 *
 * 请注意，我们不对调用者的内存上下文做任何假设。
 * ----------------
 */
void FreeExprContext(ExprContext *fc_econtext, bool fc_isCommit)
{
	EState	   *fc_estate;

	/* 调用任何注册的回调 */
	fc_ShutdownExprContext(fc_econtext, fc_isCommit);
	/* 并清理所使用的内存 */
	MemoryContextDelete(fc_econtext->ecxt_per_tuple_memory);
	/* 从拥有的EState中取消链接（如有） */
	fc_estate = fc_econtext->ecxt_estate;
	if (fc_estate)
		fc_estate->es_exprcontexts = list_delete_ptr(fc_estate->es_exprcontexts,
												  fc_econtext);
	/* 并删除ExprContext节点 */
	pfree(fc_econtext);
}

/*
 * ReScanExprContext
 *
 *		重置表达式上下文，以准备重新扫描其计划节点。
 * 这要求调用所有已注册的关机回调，
 * 因为任何部分完成的集合返回函数必须被取消。
 *
 * 请注意，我们不对调用者的内存上下文做任何假设。
 */
void ReScanExprContext(ExprContext *fc_econtext)
{
	/* 调用任何注册的回调 */
	fc_ShutdownExprContext(fc_econtext, true);
	/* 并清理所使用的内存 */
	MemoryContextReset(fc_econtext->ecxt_per_tuple_memory);
}

/*
 * 为EState构建每输出元组的ExprContext。
 *
 * 通常通过GetPerTupleExprContext()宏调用，而不是直接调用。
 */
ExprContext * MakePerTupleExprContext(EState *fc_estate)
{
	if (fc_estate->es_per_tuple_exprcontext == NULL)
		fc_estate->es_per_tuple_exprcontext = CreateExprContext(fc_estate);

	return fc_estate->es_per_tuple_exprcontext;
}


/* ----------------------------------------------------------------
 *				 杂项节点初始化支持函数
 *
 * 注意：所有这些函数预期在CurrentMemoryContext
 * 等于每查询内存上下文时调用。
 * ----------------------------------------------------------------
 */

/* ----------------
 *		ExecAssignExprContext
 *
 *		这初始化了ps_ExprContext字段。仅在使用ExecQual或ExecProject的节点
 *		中这样做是必要的，因为这些例程要求一个econtext。
 *		不必评估表达式的其他节点不需要这样做。
 * ----------------
 */
void ExecAssignExprContext(EState *fc_estate, PlanState *fc_planstate)
{
	fc_planstate->ps_ExprContext = CreateExprContext(fc_estate);
}

/* ----------------
 *		ExecGetResultType
 * ----------------
 */
TupleDesc ExecGetResultType(PlanState *fc_planstate)
{
	return fc_planstate->ps_ResultTupleDesc;
}

/*
 * ExecGetResultSlotOps - 关于节点结果插槽类型的信息
 */
const TupleTableSlotOps *
ExecGetResultSlotOps(PlanState *fc_planstate, bool *fc_isfixed)
{
	if (fc_planstate->resultopsset && fc_planstate->resultops)
	{
		if (fc_isfixed)
			*fc_isfixed = fc_planstate->resultopsfixed;
		return fc_planstate->resultops;
	}

	if (fc_isfixed)
	{
		if (fc_planstate->resultopsset)
			*fc_isfixed = fc_planstate->resultopsfixed;
		else if (fc_planstate->ps_ResultTupleSlot)
			*fc_isfixed = TTS_FIXED(fc_planstate->ps_ResultTupleSlot);
		else
			*fc_isfixed = false;
	}

	if (!fc_planstate->ps_ResultTupleSlot)
		return &TTSOpsVirtual;

	return fc_planstate->ps_ResultTupleSlot->tts_ops;
}


/* ----------------
 *		ExecAssignProjectionInfo
 *
 * 从节点的targetlist形成投影信息
 *
 * inputDesc的注释与ExecBuildProjectionInfo相同：为关系扫描节点提供它，
 * 可为上层节点传递NULL。
 * ----------------
 */
void ExecAssignProjectionInfo(PlanState *fc_planstate,
						 TupleDesc fc_inputDesc)
{
	fc_planstate->ps_ProjInfo =
		ExecBuildProjectionInfo(fc_planstate->plan->targetlist,
								fc_planstate->ps_ExprContext,
								fc_planstate->ps_ResultTupleSlot,
								fc_planstate,
								fc_inputDesc);
}


/* ----------------
 *		ExecConditionalAssignProjectionInfo
 *
 * 与ExecAssignProjectionInfo相同，但如果不需要投影，则存储NULL
 * 而不是构建投影信息。
 * ----------------
 */
void ExecConditionalAssignProjectionInfo(PlanState *fc_planstate, TupleDesc fc_inputDesc,
									int fc_varno)
{
	if (fc_tlist_matches_tupdesc(fc_planstate,
							  fc_planstate->plan->targetlist,
							  fc_varno,
							  fc_inputDesc))
	{
		fc_planstate->ps_ProjInfo = NULL;
		fc_planstate->resultopsset = fc_planstate->scanopsset;
		fc_planstate->resultopsfixed = fc_planstate->scanopsfixed;
		fc_planstate->resultops = fc_planstate->scanops;
	}
	else
	{
		if (!fc_planstate->ps_ResultTupleSlot)
		{
			ExecInitResultSlot(fc_planstate, &TTSOpsVirtual);
			fc_planstate->resultops = &TTSOpsVirtual;
			fc_planstate->resultopsfixed = true;
			fc_planstate->resultopsset = true;
		}
		ExecAssignProjectionInfo(fc_planstate, fc_inputDesc);
	}
}

static bool fc_tlist_matches_tupdesc(PlanState *fc_ps, List *fc_tlist, int fc_varno, TupleDesc fc_tupdesc)
{
	int			fc_numattrs = fc_tupdesc->natts;
	int			fc_attrno;
	ListCell   *fc_tlist_item = list_head(fc_tlist);

	/* 检查tlist属性 */
	for (fc_attrno = 1; fc_attrno <= fc_numattrs; fc_attrno++)
	{
		Form_pg_attribute fc_att_tup = TupleDescAttr(fc_tupdesc, fc_attrno - 1);
		Var		   *fc_var;

		if (fc_tlist_item == NULL)
			return false;		/* tlist过短 */
		fc_var = (Var *) ((TargetEntry *) lfirst(fc_tlist_item))->expr;
		if (!fc_var || !IsA(fc_var, Var))
			return false;		/* tlist项不是Var */
		/* 如果这些断言失败，规划器出错 */
		Assert(fc_var->varno == fc_varno);
		Assert(fc_var->varlevelsup == 0);
		if (fc_var->varattno != fc_attrno)
			return false;		/* 顺序错误 */
		if (fc_att_tup->attisdropped)
			return false;		
/* 表包含已删除的列 */
		if (fc_att_tup->atthasmissing)
			return false;		/* 表包含缺失值的列 */

		/*
		 * 注意：通常情况下，Var的类型应该与tupdesc完全匹配，但
		 * 在涉及具有不同typmods的列的联合的情况下，Var可能来自于
		 * 联合的上方，因此具有typmod -1。这是一个合法的情况，
		 * 因为Var仍然描述列，只是没有像tupdesc那样准确。我们
		 * 可以改变规划器以防止这种情况，但那样会插入
		 * 投影步骤，仅仅是为了从特定typmod转换为typmod -1，
		 * 这很愚蠢。
		 */
		if (fc_var->vartype != fc_att_tup->atttypid ||
			(fc_var->vartypmod != fc_att_tup->atttypmod &&
			 fc_var->vartypmod != -1))
			return false;		/* 类型不匹配 */

		fc_tlist_item = lnext(fc_tlist, fc_tlist_item);
	}

	if (fc_tlist_item)
		return false;			/* 任务列表太长 */

	return true;
}

/* ----------------
 *		释放表达式上下文
 *
 * 在执行器关闭期间，计划节点的表达式上下文应该被显式释放，因为可能会有关闭回调需要调用。 (上述例程所创建的其他资源，例如投影信息，不需要显式释放，因为它们只是查询内存上下文中的内存。)
 *
 * 然而... 在 ExecEndNode 期间并没有特别的必要这样做，因为 FreeExecutorState 会释放 EState 中的任何剩余表达式上下文。 让 FreeExecutorState 来做这件事允许表达式上下文按创建的反向顺序释放，而不是在这里删除时按创建顺序释放，这节省了 O(N^2) 的工作在 FreeExprContext 内部的列表清理中。
 * ----------------
 */
void ExecFreeExprContext(PlanState *fc_planstate)
{
	/*
	 * 根据上述讨论，实际上不要删除表达式上下文。我们确实
	 * 取消了它与计划节点的链接。
	 */
	fc_planstate->ps_ExprContext = NULL;
}


/* ----------------------------------------------------------------
 *				  扫描节点支持
 * ----------------------------------------------------------------
 */

/* ----------------
 *		执行分配扫描类型
 * ----------------
 */
void ExecAssignScanType(ScanState *fc_scanstate, TupleDesc fc_tupDesc)
{
	TupleTableSlot *fc_slot = fc_scanstate->ss_ScanTupleSlot;

	ExecSetSlotDescriptor(fc_slot, fc_tupDesc);
}

/* ----------------
 *		从外部计划创建扫描槽
 * ----------------
 */
void ExecCreateScanSlotFromOuterPlan(EState *fc_estate,
								ScanState *fc_scanstate,
								const TupleTableSlotOps *fc_tts_ops)
{
	PlanState  *fc_outerPlan;
	TupleDesc	fc_tupDesc;

	fc_outerPlan = outerPlanState(fc_scanstate);
	fc_tupDesc = ExecGetResultType(fc_outerPlan);

	ExecInitScanTupleSlot(fc_estate, fc_scanstate, fc_tupDesc, fc_tts_ops);
}

/* ----------------------------------------------------------------
 *		ExecRelationIsTargetRelation
 *
 *		检测关系（由 rangetable 索引识别）是否是查询的目标关系之一。
 *
 * 注意：这在核心代码中目前不再使用。我们保留它是因为 FDW 可能希望使用它来确定他们的外部表是否是目标关系。
 * ----------------------------------------------------------------
 */
bool ExecRelationIsTargetRelation(EState *fc_estate, Index fc_scanrelid)
{
	return list_member_int(fc_estate->es_plannedstmt->resultRelations, fc_scanrelid);
}

/* ----------------------------------------------------------------
 *		ExecOpenScanRelation
 *
 *		打开堆关系以供基本级别扫描计划节点扫描。
 *		这应该在节点的 ExecInit 例程中被调用。
 * ----------------------------------------------------------------
 */
Relation ExecOpenScanRelation(EState *fc_estate, Index fc_scanrelid, int fc_eflags)
{
	Relation	fc_rel;

	/* 打开关系。 */
	fc_rel = ExecGetRangeTableRelation(fc_estate, fc_scanrelid);

	/*
	 * 如果我们试图扫描一个不可扫描的关系则抱怨，除了
	 * 当查询实际上不会被执行时。 这可能是一个稍微笨拙的地方
	 * 来做这个，但没有更好的地方。
	 */
	if ((fc_eflags & (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_WITH_NO_DATA)) == 0 &&
		!RelationIsScannable(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("materialized view \"%s\" has not been populated",
						RelationGetRelationName(fc_rel)),
				 errhint("Use the REFRESH MATERIALIZED VIEW command.")));

	return fc_rel;
}

/*
 * ExecInitRangeTable
 *		设置执行器的范围表相关数据
 *
 * 除了范围表本身，初始化由 rangetable 索引索引的数组。
 */
void ExecInitRangeTable(EState *fc_estate, List *fc_rangeTable)
{
	/* 记住范围表列表原样 */
	fc_estate->es_range_table = fc_rangeTable;

	/* 设置关联数组的大小 */
	fc_estate->es_range_table_size = list_length(fc_rangeTable);

	/*
	 * 为每个 rangetable 条目分配一个数组以存储打开的关系，并将条目初始化为 NULL。关系在这里根据需要打开并存储。
	 */
	fc_estate->es_relations = (Relation *)
		palloc0(fc_estate->es_range_table_size * sizeof(Relation));

	/*
	 * es_result_relations 和 es_rowmarks 也与 es_range_table 平行，但仅在需要时分配。
	 */
	fc_estate->es_result_relations = NULL;
	fc_estate->es_rowmarks = NULL;
}

/*
 * ExecGetRangeTableRelation
 *		打开范围表条目的关系，如果尚未完成
 *
 * 关系将在 ExecEndPlan() 中关闭。
 */
Relation ExecGetRangeTableRelation(EState *fc_estate, Index fc_rti)
{
	Relation	fc_rel;

	Assert(fc_rti > 0 && fc_rti <= fc_estate->es_range_table_size);

	fc_rel = fc_estate->es_relations[fc_rti - 1];
	if (fc_rel == NULL)
	{
		/* 第一次执行，因此打开关系 */
		RangeTblEntry *fc_rte = exec_rt_fetch(fc_rti, fc_estate);

		Assert(fc_rte->rtekind == RTE_RELATION);

		if (!IsParallelWorker())
		{
			/*
			 * 在正常查询中，我们应该已经获得了适当的锁，
			 * 但通过 Assert 验证这一点。由于 table_open 内部已经有一个
			 * Assert，强调必须保持某些锁，因此在 rellockmode 高于最低值时
			 * 只检查这一点似乎足够。
			 */
			fc_rel = table_open(fc_rte->relid, NoLock);
			Assert(fc_rte->rellockmode == AccessShareLock ||
				   CheckRelationLockedByMe(fc_rel, fc_rte->rellockmode, false));
		}
		else
		{
			/*
			 * 如果我们是一个并行工作者，我们需要在关系上获得自己的本地
			 * 锁。这确保了在父进程在我们之前退出的情况下理智的行为。
			 */
			fc_rel = table_open(fc_rte->relid, fc_rte->rellockmode);
		}

		fc_estate->es_relations[fc_rti - 1] = fc_rel;
	}

	return fc_rel;
}

/*
 * ExecInitResultRelation
 *		打开由传入的 RT 索引给出的关系并填充其
 *		ResultRelInfo 节点
 *
 * 在这里，我们还将 ResultRelInfo 保存到 estate->es_result_relations 数组中
 * 以便可以稍后使用 RT 索引进行访问。
 */
void ExecInitResultRelation(EState *fc_estate, ResultRelInfo *fc_resultRelInfo,
					   Index fc_rti)
{
	Relation	fc_resultRelationDesc;

	fc_resultRelationDesc = ExecGetRangeTableRelation(fc_estate, fc_rti);
	InitResultRelInfo(fc_resultRelInfo,
					  fc_resultRelationDesc,
					  fc_rti,
					  NULL,
					  fc_estate->es_instrument);

	if (fc_estate->es_result_relations == NULL)
		fc_estate->es_result_relations = (ResultRelInfo **)
			palloc0(fc_estate->es_range_table_size * sizeof(ResultRelInfo *));
	fc_estate->es_result_relations[fc_rti - 1] = fc_resultRelInfo;

	/*
	 * 将其保存在列表中可以避免在只有少数条目可能为非 NULL 时
	 * 不必要地遍历整个数组。
	 */
	fc_estate->es_opened_result_relations =
		lappend(fc_estate->es_opened_result_relations, fc_resultRelInfo);
}

/*
 * 更新已更改参数集合
 *		将已更改的参数添加到计划节点的 chgParam 集合
 */
void UpdateChangedParamSet(PlanState *fc_node, Bitmapset *fc_newchg)
{
	Bitmapset  *fc_parmset;

	/*
	 * 计划节点仅依赖于其 allParam 集合中列出的参数。不要
	 * 将任何其他内容包含到其 chgParam 集合中。
	 */
	fc_parmset = bms_intersect(fc_node->plan->allParam, fc_newchg);

	/*
	 * 如果实际上没有任何成员，则保持 node->chgParam == NULL；这
	 * 允许在执行节点文件中进行最简单的测试。
	 */
	if (!bms_is_empty(fc_parmset))
		fc_node->chgParam = bms_join(fc_node->chgParam, fc_parmset);
	else
		bms_free(fc_parmset);
}


/*
 * executor_errposition
 *		报告执行时的光标位置（如果可能的话）。
 *
 * 这应该在 ereport() 调用中使用。返回值
 * 是一个虚拟值（实际上总是为 0）。
 *
 * 存储在解析树中的位置是源字符串中的字节偏移量。
 * 我们必须将它们转换为基于 1 的字符索引，以便报告给
 * 客户端。（我们这样做是为了避免在正常的非错误情况下
 * 产生不必要的开销：计算字符索引将比存储令牌偏移量
 * 更昂贵。）
 */
int executor_errposition(EState *fc_estate, int fc_location)
{
	int			fc_pos;

	/* 如果未提供位置则无操作 */
	if (fc_location < 0)
		return 0;
	/* 如果源文本不可用，则无法执行任何操作 */
	if (fc_estate == NULL || fc_estate->es_sourceText == NULL)
		return 0;
	/* 将偏移量转换为字符编号 */
	fc_pos = pg_mbstrlen_with_len(fc_estate->es_sourceText, fc_location) + 1;
	/*
 * 在解析完成后调用，以清理 plpgsql_scanner_init() 的残余。
 */
	return errposition(fc_pos);
}

/*
 * 在 ExprContext 中注册一个关闭回调。
 *
 * 关闭回调将在 ExprContext 被删除或重新扫描时按注册的
 * 相反顺序调用。这为在上下文中调用的函数提供了一个钩子
 * 以进行任何必要的清理——对返回集合的函数特别有用。
 * 请注意，如果执行因错误而中止，则不会调用回调。
 */
void RegisterExprContextCallback(ExprContext *fc_econtext,
							ExprContextCallbackFunction fc_function,
							Datum fc_arg)
{
	ExprContext_CB *fc_ecxt_callback;

	/* 在适当的内存上下文中保存信息 */
	fc_ecxt_callback = (ExprContext_CB *)
		MemoryContextAlloc(fc_econtext->ecxt_per_query_memory,
						   sizeof(ExprContext_CB));

	fc_ecxt_callback->function = fc_function;
	fc_ecxt_callback->arg = fc_arg;

	/* 将链接放到列表前面，以便适当的执行顺序 */
	fc_ecxt_callback->next = fc_econtext->ecxt_callbacks;
	fc_econtext->ecxt_callbacks = fc_ecxt_callback;
}

/* 
 * 在 ExprContext 中取消注册一个关机回调。
 *
 * 任何与函数和参数匹配的列表条目将被移除。
 * 如果不再需要调用该回调，可以使用此功能。
 */
void UnregisterExprContextCallback(ExprContext *fc_econtext,
							  ExprContextCallbackFunction fc_function,
							  Datum fc_arg)
{
	ExprContext_CB **fc_prev_callback;
	ExprContext_CB *fc_ecxt_callback;

	fc_prev_callback = &fc_econtext->ecxt_callbacks;

	while ((fc_ecxt_callback = *fc_prev_callback) != NULL)
	{
		if (fc_ecxt_callback->function == fc_function && fc_ecxt_callback->arg == fc_arg)
		{
			*fc_prev_callback = fc_ecxt_callback->next;
			pfree(fc_ecxt_callback);
		}
		else
			fc_prev_callback = &fc_ecxt_callback->next;
	}
}

/* 
 * 调用在 ExprContext 中注册的所有关机回调。
 *
 * 回调列表将被清空（在这种情况下很重要，因为这只是重新扫描
 * 重置，而不是删除 ExprContext）。
 *
 * 如果 isCommit 为 false，只需清理回调列表，但不调用它们。
 * （请参见 FreeExprContext 的注释。）
 */
static void fc_ShutdownExprContext(ExprContext *fc_econtext, bool fc_isCommit)
{
	ExprContext_CB *fc_ecxt_callback;
	MemoryContext fc_oldcontext;

	/* 在正常情况下，如果没有任何操作则快速路径。 */
	if (fc_econtext->ecxt_callbacks == NULL)
		return;

	/* 
	 * 在 econtext 的每个元组上下文中调用回调。 这确保了
	 * 他们可能泄漏的任何内存将被清理。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	/* 
	 * 按照反向注册顺序调用每个回调函数。
	 */
	while ((fc_ecxt_callback = fc_econtext->ecxt_callbacks) != NULL)
	{
		fc_econtext->ecxt_callbacks = fc_ecxt_callback->next;
		if (fc_isCommit)
			fc_ecxt_callback->function(fc_ecxt_callback->arg);
		pfree(fc_ecxt_callback);
	}

	MemoryContextSwitchTo(fc_oldcontext);
}

/* 
 *		GetAttributeByName
 *		GetAttributeByNum
 *
 *		这些函数返回给定元组 Datum 中请求属性的值。
 *		接受元组作为参数的 C 函数预计会使用这些。 例如：overpaid(EMP) 可能会调用 GetAttributeByNum()。
 *		注意：这些实际上相当慢，因为它们在每次调用时都进行 typcache 查找。
 */
Datum GetAttributeByName(HeapTupleHeader fc_tuple, const char *fc_attname, bool *fc_isNull)
{
	AttrNumber	fc_attrno;
	Datum		fc_result;
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupDesc;
	HeapTupleData fc_tmptup;
	int			fc_i;

	if (fc_attname == NULL)
		elog(ERROR, "invalid attribute name");

	if (fc_isNull == NULL)
		elog(ERROR, "a NULL isNull pointer was passed");

	if (fc_tuple == NULL)
	{
		/* 有点虚假，但与旧行为兼容... */
		*fc_isNull = true;
		return (Datum) 0;
	}

	fc_tupType = HeapTupleHeaderGetTypeId(fc_tuple);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_tuple);
	fc_tupDesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);

	fc_attrno = InvalidAttrNumber;
	for (fc_i = 0; fc_i < fc_tupDesc->natts; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupDesc, fc_i);

		if (namestrcmp(&(fc_att->attname), fc_attname) == 0)
		{
			fc_attrno = fc_att->attnum;
			break;
		}
	}

	if (fc_attrno == InvalidAttrNumber)
		elog(ERROR, "attribute \"%s\" does not exist", fc_attname);

	/* 
	 * heap_getattr 需要一个 HeapTuple，而不是一个裸露的 HeapTupleHeader。 我们设置结构中的所有字段，以防用户尝试检查系统列。
	 */
	fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_tuple);
	ItemPointerSetInvalid(&(fc_tmptup.t_self));
	fc_tmptup.t_tableOid = InvalidOid;
	fc_tmptup.t_data = fc_tuple;

	fc_result = heap_getattr(&fc_tmptup,
						  fc_attrno,
						  fc_tupDesc,
						  fc_isNull);

	ReleaseTupleDesc(fc_tupDesc);

	return fc_result;
}

Datum GetAttributeByNum(HeapTupleHeader fc_tuple,
				  AttrNumber fc_attrno,
				  bool *fc_isNull)
{
	Datum		fc_result;
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupDesc;
	HeapTupleData fc_tmptup;

	if (!AttributeNumberIsValid(fc_attrno))
		elog(ERROR, "invalid attribute number %d", fc_attrno);

	if (fc_isNull == NULL)
		elog(ERROR, "a NULL isNull pointer was passed");

	if (fc_tuple == NULL)
	{
		/* 有点虚假，但与旧行为兼容... */
		*fc_isNull = true;
		return (Datum) 0;
	}

	fc_tupType = HeapTupleHeaderGetTypeId(fc_tuple);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_tuple);
	fc_tupDesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);

	/* 
	 * heap_getattr 需要一个 HeapTuple，而不是一个裸露的 HeapTupleHeader。 我们设置结构中的所有字段，以防用户尝试检查系统列。
	 */
	fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_tuple);
	ItemPointerSetInvalid(&(fc_tmptup.t_self));
	fc_tmptup.t_tableOid = InvalidOid;
	fc_tmptup.t_data = fc_tuple;

	fc_result = heap_getattr(&fc_tmptup,
						  fc_attrno,
						  fc_tupDesc,
						  fc_isNull);

	ReleaseTupleDesc(fc_tupDesc);

	return fc_result;
}

/* 
 * tlist 中的项目数（包括任何 resjunk 项目！） */
int ExecTargetListLength(List *fc_targetlist)
{
	/* 这以前更复杂，但 fjoins 已死 */
	return list_length(fc_targetlist);
}

/* 
 * tlist 中的项目数，不包括任何 resjunk 项目 */
int ExecCleanTargetListLength(List *fc_targetlist)
{
	int			fc_len = 0;
	ListCell   *fc_tl;

	foreach(fc_tl, fc_targetlist)
	{
		TargetEntry *fc_curTle = lfirst_node(TargetEntry, fc_tl);

		if (!fc_curTle->resjunk)
			fc_len++;
	}
	return fc_len;
}

/* 
 * 返回 relInfo 的元组槽，用于触发器的 OLD 元组。 */
TupleTableSlot * ExecGetTriggerOldSlot(EState *fc_estate, ResultRelInfo *fc_relInfo)
{
	if (fc_relInfo->ri_TrigOldSlot == NULL)
	{
		Relation	fc_rel = fc_relInfo->ri_RelationDesc;
		MemoryContext fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

		fc_relInfo->ri_TrigOldSlot =
			ExecInitExtraTupleSlot(fc_estate,
								   RelationGetDescr(fc_rel),
								   table_slot_callbacks(fc_rel));

		MemoryContextSwitchTo(fc_oldcontext);
	}

	return fc_relInfo->ri_TrigOldSlot;
}

/* 
 * 返回 relInfo 的元组槽，用于触发器的 NEW 元组。 */
TupleTableSlot * ExecGetTriggerNewSlot(EState *fc_estate, ResultRelInfo *fc_relInfo)
{
	if (fc_relInfo->ri_TrigNewSlot == NULL)
	{
		Relation	fc_rel = fc_relInfo->ri_RelationDesc;
		MemoryContext fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

		fc_relInfo->ri_TrigNewSlot =
			ExecInitExtraTupleSlot(fc_estate,
								   RelationGetDescr(fc_rel),
								   table_slot_callbacks(fc_rel));

		MemoryContextSwitchTo(fc_oldcontext);
	}

	return fc_relInfo->ri_TrigNewSlot;
}

/* 
 * 返回 relInfo 的元组槽，用于处理返回的元组。 */
TupleTableSlot * ExecGetReturningSlot(EState *fc_estate, ResultRelInfo *fc_relInfo)
{
	if (fc_relInfo->ri_ReturningSlot == NULL)
	{
		Relation	fc_rel = fc_relInfo->ri_RelationDesc;
		MemoryContext fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

		fc_relInfo->ri_ReturningSlot =
			ExecInitExtraTupleSlot(fc_estate,
								   RelationGetDescr(fc_rel),
								   table_slot_callbacks(fc_rel));

		MemoryContextSwitchTo(fc_oldcontext);
	}

	return fc_relInfo->ri_ReturningSlot;
}

/* 
 * 返回将给定子结果关系的元组转换为查询的主要目标（“根”）关系的行类型所需的映射。 请注意，NULL 结果是有效的，意味着不需要转换。
 */
TupleConversionMap * ExecGetChildToRootMap(ResultRelInfo *fc_resultRelInfo)
{
	/* 如果我们尚未这样做，则计算此子项的映射。 */
	if (!fc_resultRelInfo->ri_ChildToRootMapValid)
	{
		ResultRelInfo *fc_rootRelInfo = fc_resultRelInfo->ri_RootResultRelInfo;

		if (fc_rootRelInfo)
			fc_resultRelInfo->ri_ChildToRootMap =
				convert_tuples_by_name(RelationGetDescr(fc_resultRelInfo->ri_RelationDesc),
									   RelationGetDescr(fc_rootRelInfo->ri_RelationDesc));
		else					/* 这不是一个子结果关系 */
			fc_resultRelInfo->ri_ChildToRootMap = NULL;

		fc_resultRelInfo->ri_ChildToRootMapValid = true;
	}

	return fc_resultRelInfo->ri_ChildToRootMap;
}

/* 返回表示被插入的列的位图 */
Bitmapset * ExecGetInsertedCols(ResultRelInfo *fc_relinfo, EState *fc_estate)
{
	/* 
	 * 列存储在范围表条目中。 如果这个 ResultRelInfo
	 * 代表一个分区路由目标，并且在范围表中没有自己的条目，则获取父级的 RTE 并将列映射到它们在分区中的顺序。
	 */
	if (fc_relinfo->ri_RangeTableIndex != 0)
	{
		RangeTblEntry *fc_rte = exec_rt_fetch(fc_relinfo->ri_RangeTableIndex, fc_estate);

		return fc_rte->insertedCols;
	}
	else if (fc_relinfo->ri_RootResultRelInfo)
	{
		ResultRelInfo *fc_rootRelInfo = fc_relinfo->ri_RootResultRelInfo;
		RangeTblEntry *fc_rte = exec_rt_fetch(fc_rootRelInfo->ri_RangeTableIndex, fc_estate);

		if (fc_relinfo->ri_RootToPartitionMap != NULL)
			return execute_attr_map_cols(fc_relinfo->ri_RootToPartitionMap->attrMap,
										 fc_rte->insertedCols);
		else
			return fc_rte->insertedCols;
	}
	else
	{
		/* 
		 * 该关系不在范围表中，并且它不是一个分区
		 * 路由目标。 这个 ResultRelInfo 只能为触发器的触发而创建，且关系并不参与插入。 （请参见 ExecGetTriggerResultRel。）
		 */
		return NULL;
	}
}

/* 返回表示被更新的列的位图 */
Bitmapset * ExecGetUpdatedCols(ResultRelInfo *fc_relinfo, EState *fc_estate)
{
	/* 请参见 ExecGetInsertedCols() */
	if (fc_relinfo->ri_RangeTableIndex != 0)
	{
		RangeTblEntry *fc_rte = exec_rt_fetch(fc_relinfo->ri_RangeTableIndex, fc_estate);

		return fc_rte->updatedCols;
	}
	else if (fc_relinfo->ri_RootResultRelInfo)
	{
		ResultRelInfo *fc_rootRelInfo = fc_relinfo->ri_RootResultRelInfo;
		RangeTblEntry *fc_rte = exec_rt_fetch(fc_rootRelInfo->ri_RangeTableIndex, fc_estate);

		if (fc_relinfo->ri_RootToPartitionMap != NULL)
			return execute_attr_map_cols(fc_relinfo->ri_RootToPartitionMap->attrMap,
										 fc_rte->updatedCols);
		else
			return fc_rte->updatedCols;
	}
	else
		return NULL;
}

/* 返回表示生成的列被更新的位图 */
Bitmapset * ExecGetExtraUpdatedCols(ResultRelInfo *fc_relinfo, EState *fc_estate)
{
	Relation	fc_rel = fc_relinfo->ri_RelationDesc;
	TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);

	if (fc_tupdesc->constr && fc_tupdesc->constr->has_generated_stored)
	{
		ListCell   *fc_lc;

		/* 如果我们尚未计算信息请进行计算 */
		if (fc_relinfo->ri_GeneratedExprs == NULL)
			ExecInitStoredGenerated(fc_relinfo, fc_estate, CMD_UPDATE);
		foreach(fc_lc, fc_estate->es_resultrelinfo_extra)
		{
			ResultRelInfoExtra *fc_rextra = (ResultRelInfoExtra *) lfirst(fc_lc);

			if (fc_rextra->rinfo == fc_relinfo)
				return fc_rextra->ri_extraUpdatedCols;
		}
		Assert(false);			/* 不应该到这里 */
	}
	return NULL;
}

/* 
 * 返回被更新的列，包括生成的列
 *
 * 位图是在每个元组内存上下文中分配的。 由调用者决定
 * 将其复制到具有适当生命周期的不同上下文中（如果需要）。
 */
Bitmapset * ExecGetAllUpdatedCols(ResultRelInfo *fc_relinfo, EState *fc_estate)
{

	Bitmapset	   *fc_ret;
	MemoryContext	fc_oldcxt;

	fc_oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(fc_estate));

	fc_ret = bms_union(ExecGetUpdatedCols(fc_relinfo, fc_estate),
					ExecGetExtraUpdatedCols(fc_relinfo, fc_estate));

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_ret;
}
